﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Configuration;
using System.Data;
using System.Data.OleDb;
using System.IO;
using System.Linq;
using System.Text;
using ARSys.Connector;
using ARSys.Entity;
using Wicresoft.Logger;

namespace Wicresoft.Task.EmployeeSync
{
    public class SyncTask : Wicresoft.Task.Common.IntervalTask
    {
        private static readonly string ARServer = ConfigurationManager.AppSettings["ARServer"];
        private static readonly string ARUid = ConfigurationManager.AppSettings["ARUid"];
        private static readonly string ARPwd = ConfigurationManager.AppSettings["ARPwd"];

        private ARSysProxy proxy;
        private ARTransaction tsc;
        private int batchCurrent;
        private int batchLimit;
        private bool enableTransaction;

        private ARTransaction GetTransaction()
        {
            if (enableTransaction) // enable transaction
            {
                if (tsc == null && proxy != null)
                    return (tsc = proxy.BeginTransaction());
                else
                    return tsc;
            }
            else // disable transaction
            {
                return null;
            }
        }
        private void ResetTransaction()
        {
            if (enableTransaction && tsc != null)
            {
                tsc.Dispose();
                tsc = null;

                batchCurrent = 0;
            }
        }
        private void CommitAndResetTransaction()
        {
            try
            {
                Log.Debug(Name, "Begin of commit {0} record(s)", batchCurrent);
                GetTransaction().Commit();
                Log.Debug(Name, "End of commit {0} record(s)", batchCurrent);
            }
            catch (Exception e)
            {
                Log.Error(Name, "Error of commit {0} record(s)", batchCurrent);
                Log.Exception(Name, e);
            }
            finally
            {
                ResetTransaction();
            }
        }

        protected override void Execute()
        {
            DateTime begin = DateTime.Now;
            Log.Info(Name, "Begin to sync employees...");

            batchLimit = int.Parse(Bag.BatchLimit);
            enableTransaction = batchLimit != 0;

            Log.Info(Name, "BuikTransaction is {0}", enableTransaction ? "enabled" : "disabled");

            using (proxy = new ARSysProxy(ARServer, ARUid, ARPwd))
            {
                using (StreamReader sr = new StreamReader(Bag.EmployeeSource))
                {
                    using (ARSysContext dc = new ARSysContext())
                    {
                        Traverse(GetDbSource(dc), GetCsvSource(sr), Push);
                    }
                }

                if (enableTransaction && batchCurrent != 0)
                {
                    CommitAndResetTransaction();
                }
            }

            DateTime end = DateTime.Now;
            var span = end - begin;

            Log.Info(Name, "End of sync employees... {0} elapsed", span);
        }

        private void Traverse(IEnumerable db, IEnumerable csv, ProceedEmployee act)
        {
            if (db == null || csv == null || act == null)
                throw new ArgumentNullException("db, csv, act");

            var t = db.GetType().GetGenericArguments();
            var k = csv.GetType().GetGenericArguments();

            // Type of csv is comiple generated, so it never equal to the one of db##
            //if (t.Length != 1 || k.Length != 1 || t.First().FullName != k.First().FullName)
            //    throw new ArgumentException("db and csv's generic type must be identical");

            //KeyAttribute
            var key = t.First().GetProperties().FirstOrDefault(p => p.HasAttribute<KeyAttribute>());
            if (key == null)
                throw new ArgumentException("db or csv's generic type must contains at least one property that is decorated with KeyAttribute.");

            // begin of algorithm
            int csvKey = 0;
            int dbKey = 0;

            IEnumerator dbEnumerator = db.GetEnumerator();
            if (dbEnumerator.MoveNext())
                dbKey = int.Parse(key.GetValue(dbEnumerator.Current, null) as string);

            foreach (var csvEntity in csv)
            {
                csvKey = int.Parse(key.GetValue(csvEntity, null) as string);

                if (dbKey == csvKey)
                {
                    // update or delete
                    act(dbEnumerator.Current, csvEntity);
                    if (dbEnumerator.MoveNext())
                        dbKey = int.Parse(key.GetValue(dbEnumerator.Current, null) as string);
                }
                else
                {
                    // create
                    act(null, csvEntity);
                }
            }
            // end of algorithm
        }

        private delegate void ProceedEmployee(object dbEntity, object csvEntity);

        private void Push(object dbEntity, object csvEntity)
        {
            var db = dbEntity as People;
            var csv = csvEntity as People;

            if (enableTransaction)
            {
                // make sure transaction initializes
                GetTransaction();
            }
            if (PushInternal(db, csv) && enableTransaction)
                batchCurrent++;

            if (enableTransaction && batchCurrent >= batchLimit)
            {
                CommitAndResetTransaction();
            }
        }

        private bool PushInternal(People db, People csv)
        {
            try
            {
                if (csv.ProfileStatus == 5)
                {
                    if (db != null)
                    {
                        // delete
                        csv.PeopleId = db.PeopleId;
                        proxy.SetEntry(csv);
                        proxy.DeleteEntry(csv);
                        Log.Info(Name, "DeleteEntry -- ID={0}, Name={1}", csv.EmployeeID, csv.Name);
                        return true;
                    }
                }
                else
                {
                    if (db == null)
                    {
                        // create
                        proxy.CreateEntry(csv);
                        Log.Info(Name, "CreateEntry -- ID={0}, Name={1}", csv.EmployeeID, csv.Name);
                        return true;
                    }
                    else
                    {
                        if (db != csv)
                        {
                            // update
                            csv.PeopleId = db.PeopleId;
                            proxy.SetEntry(csv);
                            Log.Info(Name, "SetEntry -- ID={0}, Name={1}", csv.EmployeeID, csv.Name);
                            return true;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Log.Exception(Name, e);
            }

            return false;
        }

        private IEnumerable GetCsvSource(StreamReader sr)
        {
            string pwd = Bag.DefaultPwd;

            int limit = int.Parse(Bag.SyncLimit);
            if (limit <= 0) limit = int.MaxValue;

            // Always skip the first column line!
            sr.ReadLine();

            while (limit-- > 0)
            {
                string buffer = sr.ReadLine();
                if (string.IsNullOrEmpty(buffer))
                    break;

                People emp = null;
                try
                {
                    dynamic extra = new System.Dynamic.ExpandoObject();

                    extra.Password = Bag.DefaultPwd;
                    extra.ClientType = 2000;
                    extra.LicenseType = 2;
                    extra.SupportStaff = 1;
                    extra.Company = "Alcatel-Lucent Shanghai Bell";

                    emp = People.FromRawData(buffer, extra);
                }
                catch (ArithmeticException ari)
                {
                    // by design, it means the stream reader 
                    //  hits the bottom of the csv file
                    Log.Exception(Name, ari);
                    break; // make sure we break the loop
                }
                catch (Exception e)
                {
                    if (emp != null)
                        Log.Error(Name, "Failed to read employee data {0} [{1}]", emp.EmployeeID, emp.Name);
                    Log.Exception(Name, e);
                }

                if (emp != null)
                    yield return emp;
            }
        }

        private IEnumerable GetDbSource(ARSysContext dc)
        {
            var source = dc.People.SqlQuery(@"
SELECT
    [Accounting_Number] AS [EmployeeID], 
    [Person_ID] AS [PeopleId], 
    [Remedy_Login_ID] AS [CIL], 
    [ACD_Phone_Num] AS [UPI], 
    [Nick_Name] AS [Name], 
    [First_Name] AS [FirstName], 
    [Middle_Initial] AS [MidName], 
    [Last_Name] AS [LastName], 
    [Full_Name] AS [FullName], 
    [Support_Staff] AS [SupportStaff], 
    [Profile_Status] AS [ProfileStatus], 
    [Corporate_E_Mail] AS [Email]
FROM [CTM_People]
WHERE [Accounting_Number] IS NOT NULL 
ORDER BY CONVERT(INT, [Accounting_Number])");

            return source;
        }
    }
}
